/*  
 *  Copyright 2006 Paul Jack.
 *
 *  This file is part of the Dex compiler suite. 
 *  
 *  Dex is free software; you can redistribute it and/or modify it
 *  under the terms of the GNU General Public License as published by the
 *  Free Software Foundation; either version 2 of the License, or (at your
 *  option) any later version.
 *  
 *  Dex is distributed in the hope that it will be useful, but
 *  WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
 *  Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
 */
package dex.compiler.parser;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import dex.compiler.model.base.ParseNode;
import dex.compiler.model.definition.ClassDef;
import dex.compiler.model.definition.Constructor;
import dex.compiler.model.definition.DefinitionParams;
import dex.compiler.model.definition.Function;
import dex.compiler.model.definition.Import;
import dex.compiler.model.definition.Macro;
import dex.compiler.model.definition.Member;
import dex.compiler.model.definition.Modifier;
import dex.compiler.model.definition.Record;
import dex.compiler.model.definition.Unit;
import dex.compiler.model.definition.TypedName;
import dex.compiler.model.expression.Expression;
import dex.compiler.model.statement.Block;
import dex.compiler.model.statement.Comment;
import dex.compiler.model.type.TypeNode;


/**
 * Parser for definition objects.
 */
class DefinitionParser extends StatementParser {

	
	/**
	 * Modifiers for toplevel things.  Note Dex follows Java by allowing a 
	 * "default" modifier, which is actually the absence of a modifier, and
	 * so is not present in this list.
	 */
	final private static List<String> MODIFIERS = Collections.unmodifiableList(Arrays.asList(
	 new String[] { "public", "private", "protected" }
	));


	/**
	 * Constructs a new DefinitionParser.
	 * 
	 * @param config  the configuration for the new parser
	 */
	public DefinitionParser(ParserConfig config) {
		super(config);
	}


	/**
	 * Tests for the presence of a comment.
	 * 
	 * @return  true if the next thing in the input is a comment,
	 *   false otherwise
	 */
	private boolean testComment() {
		input.skipAndPeek();
		return input.test("//", AnyBreaks.INSTANCE) || input.test("/*", AnyBreaks.INSTANCE);
	}

	
	/**
	 * Parses a list of attributes.
	 * 
	 * @return  the parsed list of attributes
	 */
	public List<String> parseAttributes() {
		List<String> result = new ArrayList<String>();
		char ch = input.skipAndPeek();
		while (ch == '@') {
			input.expect('@');
			String id = parseIdentifier(); // FIXME: No whitespace allowed between @ and id
			result.add(id);
			ch = input.skipAndPeek();
		}
		return result;
	}
	
	
	/**
	 * Parses a modifier.
	 * 
	 * @return  the parsed modifier
	 */
	public Modifier parseModifier() {
		String s = input.skipAndTest(MODIFIERS, WordBreaks.INSTANCE);
		if (s == null) {
			return Modifier.DEFAULT;
		}
		input.expect(s, WordBreaks.INSTANCE);
		return Modifier.get(s);
	}
	
	
	/**
	 * Parse a DefinitionParams.
	 * 
	 * @return  the parsed DefinitionParams
	 */
	private DefinitionParams parseDefinitionParams() {
		DefinitionParams result = new DefinitionParams();
		result.setPlace(input.getPlace());
		if (testComment()) {
			result.setComment(parseComment().getMessage());
		}
		result.setAttributes(parseAttributes());
		result.setModifier(parseModifier());
		return result;
	}
	
	
	
	/**
	 * Parses a function.
	 * 
	 * @return  the parsed function
	 */
	public Function parseFunction() {
		return parseFunction(parseTypedName());
	}

	
	/**
	 * Parses the rest of a halfway parsed function.  The given
	 * TypedName instance holds the comment, modifier, attributes,
	 * name and type of the function.  This method parses the function's
	 * parameter list as well as the function's body.
	 * 
	 * @param typedName  the typedName containing half of the required function info
	 * @return  the fully parsed function
	 */
	private Function parseFunction(TypedName typedName) {
		List<TypedName> parameters = parseTypedNameList();
		Block body = parseBlock();
		DefinitionParams dp = new DefinitionParams();
		dp.setName(typedName.getName());
		dp.setAttributes(typedName.getAttributes());
		dp.setComment(typedName.getComment());
		dp.setModifier(typedName.getModifier());
		dp.setPlace(typedName.getPlace());
		return new Function(dp, typedName.getType(), parameters, body);		
	}


	/**
	 * Parses a typed name.
	 * 
	 * @return  the parsed typed name
	 */
	public TypedName parseTypedName() {
		return parseTypedName(parseDefinitionParams());
	}
	
	
	/**
	 * Parses the rest of a typed name that's already halfway parsed.
	 * The given DefinitionParams object holds the comment, modifer
	 * and attributes for the typed name.  This method adds the name
	 * and type.
	 *  
	 * @param dp  the params holding half the parsed info
	 * @return  the fully parsed TypedName
	 */
	private TypedName parseTypedName(DefinitionParams dp) {
		TypeNode type = parseType();
		dp.setName(parseIdentifier());
		return new TypedName(dp, type);
	}


	/**
	 * Parses a list of typed names, such as a function's formal parameter list.
	 * 
	 * @return  the parsed typed name list
	 */
	private List<TypedName> parseTypedNameList() {
		List<TypedName> result = new ArrayList<TypedName>();
		input.skipAndExpect('(');
		for (char ch = input.skipAndPeek(); ch != ')'; ch = input.skipAndPeek()) {
			if (ch == 0) {
				raise("Unexpected end of input.");
			}
			if (result.size() > 0) {
				input.expect(',');
			}
			TypedName param = parseTypedName();
			result.add(param);
		}
		input.expect(')');
		return result;
	}

	/**
	 * Parses a record definition.
	 * 
	 * @return  the parsed record
	 */
	public Record parseRecord() {
		return parseRecord(parseDefinitionParams());
	}


	/**
	 * Parses the rest of a record definition that is already halfway parsed.
	 * The given DefinitionParams object holds the comment, modifer
	 * and attributes for the record.  This method adds the name
	 * of the record and its fields.
	 * 
	 * @param dp   the halfway parsed record information
	 * @return  the fully parsed record
	 */
	private Record parseRecord(DefinitionParams dp) {
		input.skipAndExpect("record", WordBreaks.INSTANCE);
		dp.setPlace(input.getPlace());
		dp.setName(parseIdentifier());
		input.skipAndExpect('{');
		List<ParseNode> contents = new ArrayList<ParseNode>();
		for (char ch = input.skipAndPeek(); ch != '}'; ch = input.skipAndPeek()) {
			if (ch == 0) {
				raise("Unterminated class definition.");
			}
			contents.add(parseContents(null, false, true));
		}
		input.expect('}');
		return new Record(dp, contents);
	}
		
	
	/**
	 * Parses the rest of a member definition that is already halfway parsed.
	 * The given DefinitionParams object holds the comment, modifer
	 * and attributes for the member.  This method adds the name
	 * of the member, figures out whether the member being defined is a 
	 * function, and, if so, parses the function information.
	 * 
	 * @param dp   the halfway parsed member information
	 * @return  the fully parsed variable or function definition
	 */
	private Member parseMember(DefinitionParams dp) {
		TypedName typedName = parseTypedName(dp);
		if (input.skipAndPeek() == '(') {
			return parseFunction(typedName);
		} else {
			return typedName;
		}
	}
	
	
	/**
	 * Parses a class definition.
	 * 
	 * @return  the parsed class definition
	 */
	public ClassDef parseClass() {
		return parseClass(parseDefinitionParams());
	}


	/**
	 * Parses the rest of a class definition that is already halfway parsed.
	 * The given DefinitionParams object holds the comment, modifer
	 * and attributes for the class.  This method adds the name of the class,
	 * its superclass and body.
	 *
	 * @param dp  the halfway parsed class information
	 * @return  the fully parsed class
	 */
	private ClassDef parseClass(DefinitionParams dp) {
		input.skipAndExpect("class", WordBreaks.INSTANCE);
		dp.setName(parseIdentifier());

		input.skipAndExpect("extends", WordBreaks.INSTANCE);
		String superclass = parseIdentifier();

		input.skipAndExpect('{');
		List<ParseNode> contents = new ArrayList<ParseNode>();
		for (char ch = input.skipAndPeek(); ch != '}'; ch = input.skipAndPeek()) {
			if (ch == 0) {
				raise("Unterminated class definition.");
			}
			contents.add(parseContents(null, false, true));
		}
		input.expect('}');
		
		return new ClassDef(dp, superclass, contents);
	}


	/**
	 * Parses a constructor for a class.
	 * 
	 * @param dp  the modifiers and attributes for the constructor
	 * @return  the parsed constructor
	 */
	private Constructor parseConstructor(DefinitionParams dp) {
		input.skipAndExpect("new", WordBreaks.INSTANCE);
		List<TypedName> parameters = parseTypedNameList();
		Block body = parseBlock();
		dp.setName("new");
		return new Constructor(dp, parameters, body);
	}


	/**
	 * Parses a macro invocation.
	 * 
	 * @return  the parsed macro invocation
	 */
	public Macro parseMacro() {
		input.skipAndExpect("macro", WordBreaks.INSTANCE);
		String name = parseIdentifier();
		List<Expression> params = parseExpressionList();
		return new Macro(input.getPlace(), name, params);
	}
	
	
	/**
	 * Parses an import.
	 * 
	 * @return  the parsed import
	 */
	public Import parseImport() {
		input.skipAndExpect("import", WordBreaks.INSTANCE);
		String name = parseIdentifier();
		return new Import(input.getPlace(), name);
	}
	
	
	/**
	 * Parses a toplevel definition or comment.  A toplevel definition is
	 * a global variable, function, class or record definition.
	 * 
	 * @return  the parsed toplevel definition or comment
	 */
	public ParseNode parseTopLevel() {
		return parseContents(null, true, true);
	}

	
	/**
	 * Parses the contents of the toplevel or of a class.
	 * The difference is that the toplevel allows user types to be
	 * defined.  
	 * 
	 * @param first      a previously parsed comment (may be null)
	 * @param userTypes  true if user type definitions should be allowed
	 * @param functions  true if function definitions should be allowed
	 * @return  either a definition or a comment
	 */
	private ParseNode parseContents(Comment first, boolean userTypes, boolean functions) {
		// If there are two comments in a row, return the first.
		Comment comment = null;
		if (first != null) {
			if (testComment()) {
				return first;
			}
			comment = first;
		} else {
			if (testComment()) {
				comment = parseComment();
				if (testComment()) {
					return comment;
				}
			}
		}
		
		if (input.skipAndPeek() == 0) {
			// The file ends with a comment, so return it.
			return comment;
		}
		
		// Otherwise we expect a definition.
		DefinitionParams dp = parseDefinitionParams();
		if (comment != null) {
			dp.setComment(comment.getMessage());
		}

		if (userTypes) {
			input.skip();
			if (input.test("class", WordBreaks.INSTANCE)) {
				return parseClass(dp);
			}
			
			if (input.test("record", WordBreaks.INSTANCE)) {
				return parseRecord(dp);
			}
		} else if (functions) {
			if (testKeyword("new")) {
				return parseConstructor(dp);
			}
		}

		try {
		if (functions) {
			return parseMember(dp);
		} else {
			return parseTypedName(dp);
		}
		} catch (ParseException e) {
			System.out.println(userTypes + "," + functions);
			throw new RuntimeException(e);
		}
	}

	
	private boolean testHeader() {
		return testKeyword("macro") || testKeyword("import") || testComment();
	}
	
	
	private ParseNode parseHeader() {
		if (testKeyword("macro")) {
			return parseMacro();
		}
		if (testKeyword("import")) {
			return parseImport();
		}
		return parseComment();
	}


	/**
	 * Parses a Dex source code file in its entirety.
	 * 
	 * @return  the contents of a Dex source code file
	 */
	public Unit parseSourceFile() {
		DefinitionParams dp = parseDefinitionParams();
		input.skipAndExpect("unit", WordBreaks.INSTANCE);
		dp.setName(parseIdentifier());
		List<ParseNode> toplevel = new ArrayList<ParseNode>();

		// Some voodoo required here to shift from the "header" section
		// (which only contains imports and macros) to the "main" section
		// (which contains variable, function, class or record definitions.
		// Since either section can contain comments, and since comments
		// are associated with main definitions, we need to pass the last
		// detected comment in the header section to the first definition
		// in the main section.
		
		Comment comment = null;
		while (testHeader()) {
			ParseNode n = parseHeader();
			if (comment != null) {
				toplevel.add(comment);
				comment = null;
			}
			if (n instanceof Comment) {
				comment = (Comment)n;
			} else {
				toplevel.add(n);
			}
		}
		
		if (input.skipAndPeek() == 0) {
			if (comment != null) {
				toplevel.add(comment);
			}
			return new Unit(dp, toplevel);
		}
		
		toplevel.add(parseContents(comment, true, true));

		for (char ch = input.skipAndPeek(); ch != 0; ch = input.skipAndPeek()) {
			ParseNode node = parseTopLevel();
			toplevel.add(node);
		}

		return new Unit(dp, toplevel);
	}

}
